package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"sort"
	"strings"
)

type NodeType int

const (
	_ NodeType = iota
	NodeTypeFile
	NodeTypeDir
)

type FileNode struct {
	children map[string]*FileNode
	opened   bool
	_type    NodeType
	path     string
	name     string
	level    int
}

// 用于排序
type FileNodePtrSlice []*FileNode

func (f FileNodePtrSlice) Len() int {
	return len(f)
}

func (f FileNodePtrSlice) Less(i, j int) bool {
	if f[i].level < f[j].level {
		return true
	} else if f[i].level > f[j].level {
		return false
	}
	return f[i].name < f[j].name
}

func (f FileNodePtrSlice) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

func anythingToJson(obj interface{}) string {
	b, _ := json.Marshal(obj)
	return string(b)
}

// 获取某节点列表（包括其本身）
func (f *FileNode) List() []*FileNode {
	// 如果是文件节点
	if f._type == NodeTypeFile {
		return []*FileNode{f}
	}
	result := FileNodePtrSlice{f}
	// 将子节点也放入列表
	if f.opened {
		for _, children := range f.children {
			result = append(result, children.List()...)
		}
	}
	sort.Sort(result)
	return []*FileNode(result)
}

// 从文件系统加载文件
func (f *FileNode) Reload() {
	f.children = map[string]*FileNode{}
	files, err := ioutil.ReadDir(f.path)
	if err != nil {
		return
	}

	for _, file := range files {
		var fileType NodeType
		if file.IsDir() {
			fileType = NodeTypeDir
		} else {
			fileType = NodeTypeFile
		}

		f.children[file.Name()] = &FileNode{
			name:   file.Name(),
			path:   f.path + "/" + file.Name(),
			opened: false,
			_type:  fileType,
			level:  f.level + 1,
		}
		if fileType == NodeTypeDir {
			f.children[file.Name()].Reload()
		}
	}
}

// 新建一个Node
func NewFileNode(path string) *FileNode {
	rootFileNode := &FileNode{
		name:   "",
		path:   path,
		opened: true, // default: True
		_type:  NodeTypeDir,
		level:  0,
	}
	rootFileNode.Reload()
	return rootFileNode
}

// 转换成可供打印的字符串数组
func NodeToStr(nodes []*FileNode) []string {
	result := []string{}
	for n, node := range nodes {
		showText := fmt.Sprintf("[%2d] ", n)
		showText += strings.Repeat(" ", node.level*2)
		if node._type == NodeTypeDir {
			if node.opened {
				showText += "-"
			} else {
				showText += "+"
			}
		} else {
			showText += " "
		}
		showText += node.name
		result = append(result, showText)
	}
	return result
}

func GetSource(node *FileNode) []TreeViewSource {
	l := node.List()
	result := []TreeViewSource{}
	for _, item := range l {
		result = append(result, item)
	}
	return result
}

func Toggle(nodes []TreeViewSource, selection int) []TreeViewSource {
	selectionNode := nodes[selection].(*FileNode)
	// 确定选中的文件类型
	if selectionNode._type == NodeTypeFile {
		return nodes
	}
	// 确定打开状态
	if selectionNode.opened {
		selectionNode.opened = false
		if selection >= len(nodes)-1 {
			return nodes
		}
		i := selection + 1
		for ; i < len(nodes); i++ {
			if nodes[i].Level() <= selectionNode.Level() {
				break
			}
		}
		return append(nodes[0:selection+1], nodes[i:]...)
	} else {
		selectionNode.opened = true
		l := GetSource(selectionNode)
		rl := []TreeViewSource{}
		rl = append(rl, nodes[0:selection]...)
		rl = append(rl, l...)
		if selection < len(nodes)-1 {
			rl = append(rl, nodes[selection+1:]...)
		}
		return rl
	}
}

func (f *FileNode) Text() string {
	return f.name
}

func (f *FileNode) Icon() string {
	if f._type == NodeTypeFile {
		return "  "
	} else {
		if f.opened {
			return "▾ "
		} else {
			return "▸ "
		}
	}
}

func (f *FileNode) Level() int {
	return f.level
}

/*
func main() {
	node := NewFileNode("/home/cuberl/workspace/go")
	b, _ := json.Marshal(node)
	fmt.Println(len(b))
	l := node.List()

	text := NodeToStr(l)
	fmt.Println(strings.Join(text, "\n"))

	server := flag.String("server", "", "vim server name")
	flag.Parse()

	fmt.Println(*server)

	for {
		d := 0
		fmt.Scanf("%d", &d)
		fmt.Println("select", d)
		if l[d].Type == NodeTypeFile {
			if *server != "" {
				fmt.Println("It will open in new tab", l[d].Path)
				cmd := exec.Command("vim", "--servername", *server, "--remote-tab", l[d].Path)
				err := cmd.Run()
				if err != nil {
					fmt.Println("cmd error: ", err)
				}
			}
		} else {
			l = Toggle(l, d)
		}
		text := NodeToStr(l)
		fmt.Println(strings.Join(text, "\n"))
	}
}

*/
